import { ObjectTool } from '@es-tool/core'
import { GetOneRes } from 'database-ql/src/result-types'
import { Db } from 'laf-client-sdk'
import { Sealed } from './annotation/Sealed'
import { ResponseStatusException } from './exception/ResponseStatusException'
import { LafWrapperConfig } from './LafWrapperConfig'
import { Logger } from './log/Logger'
import { QueryChainWrapper } from './QueryChainWrapper'
import { UpdateChainWrapper } from './UpdateChainWrapper'

// noinspection JSUnusedGlobalSymbols
/**
 * LafClient
 * @author LL
 * @date 2022-01-22 下午 12:55
 **/
@Sealed
export class LafClient<E> {
    private readonly tableName: string
    private readonly database: Db
    private log: Logger

    constructor(tableName: string, database?: Db) {
        this.tableName = tableName
        this.database = database as Db ?? LafWrapperConfig.database()
        this.log = LafWrapperConfig.LoggerFactory.getLogger(`LafClient#${ this.tableName }`)
    }

    /**
     * 根据ID查询
     * @param {string} id ID
     * @return {Promise<E> | null} 查询到的数据 或者 null
     */
    public async selectById(id: string): Promise<E | null> {
        this.log.trace('SelectById: ', id)
        if (ObjectTool.isNull(id)) {
            throw new Error('非法参数: ' + id)
        }
        const res: GetOneRes<E> = await this.getCollection()
            .doc(id)
            .get<E>()
        if (ObjectTool.isNotEmpty(res) && res.ok) {
            this.log.trace('SelectById:', res.data)
            return res.data
        }
        throw new ResponseStatusException('selectById 错误: ' + res)
    }

    /**
     * 根据ID更新
     * 如果需要移除更新数据中的 ID 允许如下写法:
     * <pre>
     *     client.updateById(entity.id, entity, 'id')
     * </pre>
     * 如果无需此操作, 省略第三个参数即可
     * @param {string | number} id 数据ID
     * @param {E} data 待更新数据, 如果包含ID, 需要传递第三个参数, 指定id 属性名
     * @param removeIdKey {string} 需要移除的ID属性名, 可省略
     * @return {Promise<boolean>} 更新成功
     */
    public async updateById(id: string | number, data: Partial<E>, removeIdKey?: keyof E): Promise<boolean> {
        this.log.trace(`UpdateById: ID: ${ id }, DATA:`, data)
        if (ObjectTool.isNull(data)) {
            throw new Error('非法参数')
        }

        let noKey = ObjectTool.isNull(removeIdKey)
        const upData = noKey ? data : ObjectTool.copy<E>(data as E)
        if (!noKey) {
            delete upData[String(removeIdKey)]
        }
        const res = await this.getCollection().doc(id).update(upData)
        if (ObjectTool.isNotEmpty(res) && res.ok) {
            const { matched, updated } = res
            this.log.trace(`UpdateBy: ${ id }, 匹配: ${ matched } 更新: ${ updated }`)

            return updated === 1
        }

        throw new ResponseStatusException('UpdateById 错误: ' + res)
    }

    /**
     * 指定ID删除
     * @param {string | number} id 待删除的ID
     * @return {Promise<boolean>} 删除成功?
     */
    public async deleteById(id: string | number): Promise<boolean> {
        this.log.trace('DeleteById: ', id)
        if (ObjectTool.isNull(id)) {
            throw new Error('非法参数:' + id)
        }

        const res = await this.getCollection().doc(id).remove()

        if (ObjectTool.isNotEmpty(res) && res.ok) {
            const { deleted } = res
            this.log.trace(`DeleteBy ${ id } 删除数量: ${ deleted }`)

            return deleted > 0
        }

        throw new ResponseStatusException('DeleteById 响应错误: ' + res)
    }

    /**
     * 新增
     * @param {Partial<E>} data 新增数据
     * @return {Promise<string | number>} 新增的ID
     */
    public async insert(data: Partial<E>): Promise<string | number> {
        this.log.trace('Insert: ', data)
        const res = await this.getCollection().add(data)
        if (ObjectTool.isNotEmpty(res) && res.ok) {
            const { insertedCount, id } = res
            // insertedCount 不可信; 大概率没有值
            this.log.trace(`Insert 响应: id = ${ id }, count = ${ insertedCount }`)
            if (ObjectTool.isNull(id)) {
                throw new Error('Insert 失败')
            }
            return id
        }

        throw new ResponseStatusException('Insert 响应错误: ' + res)
    }

    /**
     * 使用当前表名 新建 {@link QueryChainWrapper} 实例
     * @return {QueryChainWrapper<E>} 新实例
     */
    public queryWrapper(): QueryChainWrapper<E> {
        return new QueryChainWrapper<E>(this.tableName, this.database)
    }

    /**
     * 使用当前表名 新建 {@link UpdateChainWrapper} 实例
     * @return {UpdateChainWrapper<E>} 新实例
     */
    public updateWrapper(): UpdateChainWrapper<E> {
        return new UpdateChainWrapper<E>(this.tableName, this.database)
    }

    private getCollection() {
        return this.database.collection(this.tableName)
    }
}
